/*
 *  scoreBoard - BoardModel.cpp
 *  Copyright 2011 W. Reckman. All rights reserved.
 */
#include <math.h>
#include <algorithm>
#include <functional>
#include "ofxDirList.h"
#include "PlayerHead.h"
#include "ScoreBoardApp.h"
#include "BoardModel.h"


const int BoardModel::BALANCE_MAX = 100; //range is always 0 - BALANCE_MAX, neutral is always BALANCE_MAX / 2
const float BoardModel::PLAYER_IMAGE_DIMENSION_RATIO = 4.0f / 3.0f;
const float BoardModel::PLAYER_TOP_TO_TOTAL_RATIO =  34.0f / ( 34.0f + 14.0f );
const float BoardModel::LANE_THICKNESS = 3.0f;

//--------------------------------------------------------------
BoardModel::BoardModel( ScoreBoardApp& appRef_ )
: p0Colliding( false ), //TEMP
s( Settings::getInstance() ), appRef( appRef_ ), gameTime( ofGetElapsedTimef() ),
  currentGameNum( 0 ), gameState( GS_IDLE )
{}

BoardModel::~BoardModel() {
	//TODO: delete playerHeads (and delete imagepaths inside too)
}


//--------------------------------------------------------------
void BoardModel::setup() {
	initLookupTables();
	
	basePlayerSpeed = ofToInt( *s.getValue( "base_player_speed", "65" ) );
	baseBiteSpeed = ofToInt( *s.getValue( "base_bite_speed", "100" ) );
	//stageLaneBottomY = ofToInt( *s.getValue( "grid_stage_bottom_y", "0" ) );
	playerYSubtract = ofToInt( *s.getValue( "grid_player_y_subtract", "0" ) );
	
	playerDrawSize.x = ofToInt( *s.getValue("grid_lane_width", "64" ) );
	playerDrawSize.y = ( 1.0f / PLAYER_IMAGE_DIMENSION_RATIO ) * playerDrawSize.x;
	
	balance.setPosition( lutColumns[0] + 60, lutRows[lutRows.size() - 1] + 50 );
}

//--------------------------------------------------------------
void BoardModel::update() {
	float lastGameTime = gameTime;
	gameTime = ofGetElapsedTimef();
	
//NOTE: TEMP: keep for reference
//	if ( playTimeLeft > 0.0f ) {
//		playTimeLeft -= gameTime - lastGameTime;
//		if ( playTimeLeft <= 0.0f ) playTimeLeft = 0.0f;
//	}
	
	std::for_each ( playerHeads.begin(), playerHeads.end(), std::mem_fun( &PlayerHead::update ) );
}

//--------------------------------------------------------------
const ofColor& BoardModel::getPlayerColor( int playerIdx_, PLAYER_COLOR_BRIGHTNESS brightness_ ) {
	static const float dimFactor = 0.7f;
	static ofColor color[3], dimmed[3];
	color[0].r = 0x54; color[0].g = 0xff; color[0].b = 0x0; /* green */
	color[1].r = 0xff; color[1].g = 0x0; color[1].b = 0xfd; /* purple */
	color[2].r = 0xff; color[2].g = 0x97; color[2].b = 0x0; /* orange */
	
	dimmed[0].r = color[0].r * dimFactor; dimmed[0].g = color[0].g * dimFactor; dimmed[0].b = color[0].b * dimFactor; /* green */
	dimmed[1].r = color[1].r * dimFactor; dimmed[1].g = color[1].g * dimFactor; dimmed[1].b = color[1].b * dimFactor; /* purple */
	dimmed[2].r = color[2].r * dimFactor; dimmed[2].g = color[2].g * dimFactor; dimmed[2].b = color[2].b * dimFactor; /* orange */
	
	switch ( brightness_ ) {
		case PCB_DIMMED:
			return ( playerIdx_ >= 0 && playerIdx_ < 3 ) ? dimmed[playerIdx_] : dimmed[0];
			break;
		case PCB_NORMAL:
			return ( playerIdx_ >= 0 && playerIdx_ < 3 ) ? color[playerIdx_] : color[0];
	}
}

//--------------------------------------------------------------
float BoardModel::getTime() const { return gameTime; }
int BoardModel::getCurrentGameNum() const { return currentGameNum; }
const BoardModel::vec_int& BoardModel::getRowLanes() const { return lutRows; }
const BoardModel::vec_int& BoardModel::getColumnLanes() const { return lutColumns; }
const ofRectangle& BoardModel::getBoundingBox() const { return boundingBox; }
const BoardModel::vec_PlayerHeadP& BoardModel::getPlayerHeads() const { return playerHeads; }
BoardModel::vec_PlayerHeadP& BoardModel::getPlayerHeads() { return playerHeads; }
const PlayerHead* BoardModel::getPlayerHead( int index_ ) const { return playerHeads.size() > index_ ? playerHeads[index_] : 0; };
PlayerHead* BoardModel::getPlayerHead( int index_ ) { return playerHeads.size() > index_ ? playerHeads[index_] : 0; };
const BoardModel::vec_EndGameStat& BoardModel::getEndGameStats() const { return endGameStats; }

const BalanceMeter& BoardModel::getBalanceMeter() const { return balance; }
BalanceMeter& BoardModel::getBalanceMeter() { return balance; }

//--------------------------------------------------------------
int BoardModel::getBasePlayerSpeed() const { return basePlayerSpeed; }
int BoardModel::getBaseBiteSpeed() const { return baseBiteSpeed; }
int BoardModel::getPlayerYSubtract() const { return playerYSubtract; }
//int BoardModel::getStageLaneBottomY() const { return stageLaneBottomY; }

//--------------------------------------------------------------
const ofPoint& BoardModel::getPlayerDrawSize() const { return playerDrawSize; }

//--------------------------------------------------------------
void BoardModel::setBalance( float balance_ ) {
	ofLog( OF_LOG_NOTICE, "(BoardModel.cpp) changing balance: %.2f -> %.2f", balance.getBalanceFloat(), balance_ );
	balance.setBalanceFloat( balance_ );
}

//--------------------------------------------------------------
void BoardModel::changeGameState( GAME_STATE oldState_, GAME_STATE newState_ ) {
	GAME_STATE oldState = gameState;
	gameState = newState_;
	ofLog( OF_LOG_NOTICE, "(BoardModel.cpp) game state changed: %s -> %s", gameStateText( oldState ).c_str(), gameStateText( gameState ).c_str() );
	
	//FIXME: should keep prevState too and do this in update()
	if ( gameState == GS_END ) {
		PlayerHead* winner = 0;
		float score = 100;
		
		float gb = getBalanceMeter().getBalanceFloat();
		for ( int i = 0; i < playerHeads.size(); ++i ) {
			PlayerHead* p = playerHeads[i];
			if ( p->getGameNumber() == currentGameNum ) {
				BalanceMeter bm; bm.setBalanceCulture( p->getInfo().cultureTgt );
				float ps = fabsf( bm.getBalanceFloat() - gb );
				if ( ps < score ) {
					winner = p;
					score = ps;
				}
			}
		}
		
		if ( winner ) {
			EndGameStat egs;
			egs.winner = winner;
			egs.balance = gb;
			egs.gameNum = currentGameNum;
			endGameStats.push_back( egs );
			
			//FIXME: this shouldn't be here either of course...
			setWinnerMovePath( winner );
			ofLog( OF_LOG_NOTICE, "(BoardModel.cpp) a game ended, added player %i as winner", winner->getInfo().id + 1 );
			
		} else {
			ofLog( OF_LOG_WARNING, "(BoardModel.cpp) a game ended, but no players have been received...nothing added to endgame stats" );
		}
		
		currentGameNum++;
	}
}

//--------------------------------------------------------------
PlayerHead* BoardModel::createPlayerHead( const PlayerInfo& info_ ) {
	ofLog( OF_LOG_NOTICE, "(BoardModel.cpp) creating player head" );
	
	ImageFileManager::vec_PlayerInfo& qii = appRef.getImageFileManager().getQueuedInfoItems();
	int found = -1;
	for ( int i = 0; i < qii.size(); ++i ) {
		if ( playerInfosEqual( qii[i], info_ ) ) {
			found = i;
			break;
		}
	}
	
	if ( found > -1 ) {
		ofLog( OF_LOG_NOTICE, "(BoardModel.cpp) found matching player images %i", found );
		qii[found].goodPickups = info_.goodPickups;
		qii[found].badPickups = info_.badPickups;
		
		delete info_.topImagePath; delete info_.bottomImagePath;
		
		PlayerHead* ph = new PlayerHead( appRef );
		ph->setup( qii[found], currentGameNum );
		playerHeads.push_back( ph );
		
		return ph;
	} else {
		ofLog( OF_LOG_NOTICE, "(BoardModel.cpp) could not find matching player images" );
		return 0;
	}
}

//--------------------------------------------------------------
bool BoardModel::dirCoordsAreOnGrid( const ofPoint& c_, DIRECTION dir_,
																		int laneMargin_, int intersectMargin_ ) const {
	bool hitFound = false;
	bool colIntersectHit = false;
	int minColX = lutColumns[0];
	int maxColX = lutColumns[lutColumns.size() - 1];
	int minRowY = lutRows[0];
	//int maxRowY = includeStageLanes_ ? stageLaneBottomY : lutRows[lutRows.size() - 1];
	int maxRowY = lutRows[lutRows.size() - 1];
	
	//do not allow intersection margin to be smaller than lane margin
	if ( intersectMargin_ < laneMargin_ ) intersectMargin_ = laneMargin_;
	
	//first look for hits on column lanes, and take note of any possible intersection hits
	for ( int xi = 0; xi < lutColumns.size(); ++xi ) {
		int xDelta = fabs( c_.x - lutColumns[xi] );
		bool colLaneHit = ( xDelta <= LANE_THICKNESS + laneMargin_ );
		colIntersectHit = ( xDelta <= LANE_THICKNESS + intersectMargin_ );
		
		if ( colLaneHit && c_.y >= minRowY && c_.y <= maxRowY ) {
			if ( dir_ == D_UP || dir_ == D_DOWN ) {
				hitFound = true;
				break;
			}
		}
	}
	
	//if no column lane hit was found, look for row hits and also check if any intersection hits match
	if ( ! hitFound ) {
		for ( int yi = 0; yi < lutRows.size(); ++yi ) {
			int yDelta = fabs( c_.y - lutRows[yi] );
			bool rowLaneHit = ( yDelta <= LANE_THICKNESS + laneMargin_ );
			bool rowIntersectHit = ( yDelta <= LANE_THICKNESS + intersectMargin_ );
			
			if ( rowLaneHit  && c_.x >= minColX && c_.x <= maxColX ) {
				if ( dir_ == D_LEFT || dir_ == D_RIGHT ) {
					hitFound = true;
					break;
				}
			}
			if ( colIntersectHit && rowIntersectHit ) {
				hitFound = true;
				break;
			}
		}
	}
	
	return hitFound;
}

//--------------------------------------------------------------
bool BoardModel::generateRandomPositionOnGrid( ofPoint& pos_ ) const {
	int minColX = lutColumns[0];
	int maxColX = lutColumns[lutColumns.size() - 1];
	int minRowY = lutRows[0];
	//int maxRowY = includeStageLanes_ ? stageLaneBottomY : lutRows[lutRows.size() - 1];
	int maxRowY = lutRows[lutRows.size() - 1];
	
	bool colLane = ofRandom( 0, 1 );
	
	if ( colLane ) {
		int laneNum = ofRandom( 0, lutColumns.size() - 1 );
		pos_.x = lutColumns[laneNum];
		pos_.y = ofRandom( minRowY, maxRowY );
	} else {
		int laneNum = ofRandom( 0, lutRows.size() - 1 );
		pos_.x = ofRandom( minColX, maxColX );
		pos_.y = lutRows[laneNum];
	}
	
	return true;
}


int BoardModel::getNearLane( const GridPoint<int>& p_, NEAR_LANE which_, bool returnIndex_ ) const {
	float result = -1;
	
	if ( which_ == NL_PREV_X || which_ == NL_NEXT_X ) {
		BoardModel::vec_int cl = getColumnLanes();
		
		if ( which_ == NL_PREV_X ) {
			for ( int i = cl.size() - 1; i >= 0; --i ) {
				if ( cl[i] <= p_.x ) {
					result = returnIndex_ ? i : cl[i];
					break;
				}
			}
		} else {
			for ( int i = 0; i < cl.size(); ++i ) {
				if ( cl[i] >= p_.x ) {
					result = returnIndex_ ? i : cl[i];
					break;
				}
			}
		}
		
	} else {
		BoardModel::vec_int rl = getRowLanes();
		
		if ( which_ == NL_PREV_Y ) {
			for ( int i = rl.size() - 1; i >= 0; --i ) {
				if ( rl[i] <= p_.y ) {
					result = returnIndex_ ? i : rl[i];
					break;
				}
			}
		} else {
			for ( int i = 0; i < rl.size(); ++i ) {
				if ( rl[i] >= p_.y ) {
					result = returnIndex_ ? i : rl[i];
					break;
				}
			}
		}
	}
	
	return result;
}

bool BoardModel::fillInLaneIndices( GridPoint<int>& gp_, int laneMargin_, bool fixCoords_ ) const {
	int colHit = -1, rowHit = -1;
	vec_int colLanes = getColumnLanes();
	vec_int rowLanes = getRowLanes();
	int minColX = colLanes[0];
	int maxColX = colLanes[colLanes.size() - 1];
	int minRowY = rowLanes[0];
	//int maxRowY = getStageLaneBottomY();
	int maxRowY = rowLanes[rowLanes.size() - 1];
	
	//first look for hits on column lanes
	for ( int xi = 0; xi < colLanes.size(); ++xi ) {
		int xDelta = fabs( gp_.x - colLanes[xi] );
		bool colLaneHit = ( xDelta <= BoardModel::LANE_THICKNESS + laneMargin_ );
		
		if ( colLaneHit && gp_.y >= minRowY && gp_.y <= maxRowY ) { colHit = xi; break; }
	}
	
	//if no column lane hit was found, look for row hits
	for ( int yi = 0; yi < rowLanes.size(); ++yi ) {
		int yDelta = fabs( gp_.y - rowLanes[yi] );
		bool rowLaneHit = ( yDelta <= BoardModel::LANE_THICKNESS + laneMargin_ );
		
		if ( rowLaneHit  && gp_.x >= minColX && gp_.x <= maxColX ) { rowHit = yi; break; }
	}
	
	gp_.colIdx = colHit;
	gp_.rowIdx = rowHit;
	
	if ( fixCoords_ ) {
		if ( colHit >= 0 ) gp_.x = colLanes[colHit];
		if ( rowHit >= 0 ) gp_.y = rowLanes[rowHit];
	}
	
	return colHit >= 0 || rowHit >= 0;
}

//--------------------------------------------------------------
bool BoardModel::playerInfosEqual( const PlayerInfo& p1, const PlayerInfo& p2 ) const {
	return p1.month == p2.month && p1.day == p2.day && p1.hours == p2.hours &&
	p1.minutes == p2.minutes && p1.seconds == p2.seconds &&
	p1.cultureTgt == p2.cultureTgt && p1.industryTgt == p2.industryTgt;
}


/*********************
 * PRIVATE FUNCTIONS *
 *********************/

//--------------------------------------------------------------
void BoardModel::setWinnerMovePath( PlayerHead* ph ) {
	const vec_int& rl = getRowLanes();
	const vec_int& cl = getColumnLanes();
	
	float minX = cl[0], maxX = cl[cl.size() - 1];
	float minY = rl[0], maxY = rl[rl.size() - 1] - 30;
	float midY = rl[1];
	
	int lane = roundf( ofRandom( 0,  cl.size() ) ); //choose between columns or center row
	ofLog( OF_LOG_NOTICE, "(BoardModel.cpp) chose lane: %i (between %i and %i)", lane, 0, cl.size() );
	
	if ( lane < cl.size() ) { //use lane as index into cl
		ph->setXRange( cl[lane], cl[lane] );
		ph->setYRange( minY, maxY );
		ph->setPosition( cl[lane], ofRandom( minY + 40, maxY - 40 ) );
	} else { //use center row lane
		ph->setXRange( minX, maxX );
		ph->setYRange( midY, midY );
		ph->setPosition( ofRandom( minX + 40, maxX - 40 ), midY );
	}
	ph->setDrawingEnabled( true );
}

//--------------------------------------------------------------
void BoardModel::initLookupTables() {
	int gridCols = ofToInt( *s.getValueInGroup( "grid_columns", "grid", "-1" ) );
	int gridRows = ofToInt( *s.getValueInGroup( "grid_rows", "grid", "-1" ) );
	int gridLaneWidth = ofToInt( *s.getValueInGroup( "grid_lane_width", "grid", "-1" ) );
	boundingBox.x = ofToInt( *s.getValueInGroup( "grid_bb_x", "grid", "-1" ) );
	boundingBox.y = ofToInt( *s.getValueInGroup( "grid_bb_y", "grid", "-1" ) );
	boundingBox.width = ofToInt( *s.getValueInGroup( "grid_bb_w", "grid", "-1" ) );
	boundingBox.height = ofToInt( *s.getValueInGroup( "grid_bb_h", "grid", "-1" ) );
	
	float vertSpace = boundingBox.height / ( gridRows - 1 );
	float horSpace = boundingBox.width / ( gridCols - 1 );
	
	for ( float xp = boundingBox.x; xp <= boundingBox.x + boundingBox.width; xp += horSpace ) {
		lutColumns.push_back( xp );
	}
	
	for ( float yp = boundingBox.y; yp <= boundingBox.y + boundingBox.height; yp += vertSpace ) {
		lutRows.push_back( yp );
	}
}
